import DrawBaseItem from './DrawBaseItem';
import PathD2NodeUtil from './utils/PathD2NodeUtil';
import {Polygon} from 'pixi.js';

import BezierUtils from './utils/BezierUtils';
import ArcUtils from './utils/ArcUtils';
import QuadraticUtils from './utils/QuadraticUtils';

/**
 * Path
 *
 * PathItem
 * 
 * @extends DrawBaseItem
 * @class
 * @memberof Nice.DrawBase
 * @author iwangcj
 */
export default class PathItem extends DrawBaseItem {
    constructor(d) {
        super();

        /**
         * Current path
         *
         * @member {PIXI.Polygon}
         * @protected
         */
        this.currentPath = null;
        
        this.d = "";
        this.nodeList = [];
        this.setPath(d);

    }

    getPath() {
        return this.currentPath;
    }

    setPath(d) {
        if (d && typeof d ==='string') {
            this.d = d;
            this.nodeList = PathD2NodeUtil.convert(d);
            this.currentPath = new Polygon();
            this.currentPath.closeStroke = false;
        }
    }

    _redraw() {
        const nodelist = this.nodeList;
        for (let i=0; i<nodelist.length; i++) {
            const node = nodelist[i];
            const CMD = node.cmd.toUpperCase();
            if (CMD === 'M') {
                const {x, y} = node;
                this.moveTo(x, y);
                // console.log(CMD, x, y);
            }
            else if (CMD === 'C') {
                const {cx1,cy1,cx2,cy2,x,y} = node;
                this.bezierCurveTo(cx1, cy1, cx2, cy2, x, y);
                // console.log(CMD, x, y);
            }
            else if (CMD === 'L') {
                const {x, y} = node;
                this.lineTo(x, y);
            }
            else if (CMD === 'Q') {
                const {cx,cy,x,y} = node;
                this.quadraticCurveTo(cx,cy,x,y);
            }
            else if (CMD === 'A') {
                const {rx,ry,arc,rotation,sweep, x,y} = node;
                this.arcTo(rx,ry,x,y,arc);
            }
            else if (CMD === "H") {
                const {x} = node; 
                this.lineTo(x, nodelist[i-1].y);
            }
            else if (CMD === "V") {
                const {y} = node; 
                this.lineTo(nodelist[i-1].x, y);    
            }
        }
    }

    draw() {
        this._redraw();
        this.drawShape(this.currentPath);
    }

    /**
     * Moves the current drawing position to x, y.
     *
     * @param {number} x - the X coordinate to move to
     * @param {number} y - the Y coordinate to move to
     * @return {PIXI.DrawBaseItem} This DrawBaseItem object. Good for chaining method calls
     */
    moveTo(x, y)
    {
        this.startPoly();
        this.currentPath.points[0] = x;
        this.currentPath.points[1] = y;

        return this;
    }

    /**
     * Draws a line using the current line style from the current drawing position to (x, y);
     * The current drawing position is then set to (x, y).
     *
     * @param {number} x - the X coordinate to draw to
     * @param {number} y - the Y coordinate to draw to
     * @return {PIXI.DrawBaseItem} This DrawBaseItem object. Good for chaining method calls
     */
    lineTo(x, y)
    {
        if (!this.currentPath)
        {
            this.moveTo(0, 0);
        }

        // remove duplicates..
        const points = this.currentPath.points;
        const fromX = points[points.length - 2];
        const fromY = points[points.length - 1];

        if (fromX !== x || fromY !== y)
        {
            points.push(x, y);
        }

        return this;
    }

    /**
     * Initialize the curve
     *
     * @protected
     * @param {number} [x=0]
     * @param {number} [y=0]
     */
    _initCurve(x = 0, y = 0)
    {
        if (this.currentPath)
        {
            if (this.currentPath.points.length === 0)
            {
                this.currentPath.points = [x, y];
            }
        }
        else
        {
            this.moveTo(x, y);
        }
    }

    /**
     * Calculate the points for a quadratic bezier curve and then draws it.
     * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c
     *
     * @param {number} cpX - Control point x
     * @param {number} cpY - Control point y
     * @param {number} toX - Destination point x
     * @param {number} toY - Destination point y
     * @return {PIXI.DrawBaseItem} This DrawBaseItem object. Good for chaining method calls
     */
    quadraticCurveTo(cpX, cpY, toX, toY)
    {
        this._initCurve();

        const points = this.currentPath.points;

        if (points.length === 0)
        {
            this.moveTo(0, 0);
        }

        QuadraticUtils.curveTo(cpX, cpY, toX, toY, points);

        return this;
    }

    /**
     * Calculate the points for a bezier curve and then draws it.
     *
     * @param {number} cpX - Control point x
     * @param {number} cpY - Control point y
     * @param {number} cpX2 - Second Control point x
     * @param {number} cpY2 - Second Control point y
     * @param {number} toX - Destination point x
     * @param {number} toY - Destination point y
     * @return {PIXI.DrawBaseItem} This DrawBaseItem object. Good for chaining method calls
     */
    bezierCurveTo(cpX, cpY, cpX2, cpY2, toX, toY)
    {
        this._initCurve();

        BezierUtils.curveTo(cpX, cpY, cpX2, cpY2, toX, toY, this.currentPath.points);

        return this;
    }

    /**
     * The arcTo() method creates an arc/curve between two tangents on the canvas.
     *
     * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google!
     *
     * @param {number} x1 - The x-coordinate of the first tangent point of the arc
     * @param {number} y1 - The y-coordinate of the first tangent point of the arc
     * @param {number} x2 - The x-coordinate of the end of the arc
     * @param {number} y2 - The y-coordinate of the end of the arc
     * @param {number} radius - The radius of the arc
     * @return {PIXI.DrawBaseItem} This DrawBaseItem object. Good for chaining method calls
     */
    arcTo(x1, y1, x2, y2, radius)
    {
        this._initCurve(x1, y1);

        const points = this.currentPath.points;

        const result = ArcUtils.curveTo(x1, y1, x2, y2, radius, points);

        if (result)
        {
            const { cx, cy, radius, startAngle, endAngle, anticlockwise } = result;

            this.arc(cx, cy, radius, startAngle, endAngle, anticlockwise);
        }

        return this;
    }

    /**
     * The arc method creates an arc/curve (used to create circles, or parts of circles).
     *
     * @param {number} cx - The x-coordinate of the center of the circle
     * @param {number} cy - The y-coordinate of the center of the circle
     * @param {number} radius - The radius of the circle
     * @param {number} startAngle - The starting angle, in radians (0 is at the 3 o'clock position
     *  of the arc's circle)
     * @param {number} endAngle - The ending angle, in radians
     * @param {boolean} [anticlockwise=false] - Specifies whether the drawing should be
     *  counter-clockwise or clockwise. False is default, and indicates clockwise, while true
     *  indicates counter-clockwise.
     * @return {PIXI.DrawBaseItem} This DrawBaseItem object. Good for chaining method calls
     */
    arc(cx, cy, radius, startAngle, endAngle, anticlockwise = false)
    {
        if (startAngle === endAngle)
        {
            return this;
        }

        if (!anticlockwise && endAngle <= startAngle)
        {
            endAngle += PI_2;
        }
        else if (anticlockwise && startAngle <= endAngle)
        {
            startAngle += PI_2;
        }

        const sweep = endAngle - startAngle;

        if (sweep === 0)
        {
            return this;
        }

        const startX = cx + (Math.cos(startAngle) * radius);
        const startY = cy + (Math.sin(startAngle) * radius);
        const eps = this.geometry.closePointEps;

        // If the currentPath exists, take its points. Otherwise call `moveTo` to start a path.
        let points = this.currentPath ? this.currentPath.points : null;

        if (points)
        {
            // TODO: make a better fix.

            // We check how far our start is from the last existing point
            const xDiff = Math.abs(points[points.length - 2] - startX);
            const yDiff = Math.abs(points[points.length - 1] - startY);

            if (xDiff < eps && yDiff < eps)
            {
                // If the point is very close, we don't add it, since this would lead to artifacts
                // during tessellation due to floating point imprecision.
            }
            else
            {
                points.push(startX, startY);
            }
        }
        else
        {
            this.moveTo(startX, startY);
            points = this.currentPath.points;
        }

        ArcUtils.arc(startX, startY, cx, cy, radius, startAngle, endAngle, anticlockwise, points);

        return this;
    }

    /**
     * Start a polygon object internally
     * @protected
     */
    startPoly()
    {
        if (this.currentPath)
        {
            const points = this.currentPath.points;
            const len = this.currentPath.points.length;

            if (len > 2)
            {
                this.drawShape(this.currentPath);
                this.currentPath = new Polygon();
                this.currentPath.closeStroke = false;
                this.currentPath.points.push(points[len - 2], points[len - 1]);
            }
        }
        else
        {
            this.currentPath = new Polygon();
            this.currentPath.closeStroke = false;
        }
    }

    /**
     * Finish the polygon object.
     * @protected
     */
    finishPoly()
    {
        if (this.currentPath)
        {
            if (this.currentPath.points.length > 2)
            {
                this.drawShape(this.currentPath);
                this.currentPath = null;
            }
            else
            {
                this.currentPath.points.length = 0;
            }
        }
    }

    /**
     * Closes the current path.
     *
     * @return {PIXI.DrawBaseItem} Returns itself.
     */
    closePath()
    {
        const currentPath = this.currentPath;

        if (currentPath)
        {
            // we don't need to add extra point in the end because buildLine will take care of that
            currentPath.closeStroke = true;
        }

        return this;
    }

    /**
     * get/set 属性
     * @param {*} key 
     * @param {*} value 
     */
    attr(key, value) {
        if (undefined != value) {
            let isOk = this.attrBase(key, value);
            !isOk && (isOk = this.attrStyle(key, value)); 

            if (isOk) {
                this.draw();
            }
        }
        else {
            let result = this.attrBase(key);
            undefined == result && (result = this.attrStyle(key));
            return result;
        }
    }
}